{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"text-align: right\"><i>Peter Norvig, 2022</i></div> \n",
    "\n",
    "# The Babylonian Number System\n",
    "\n",
    "Our number system has ten digit symbols, 0 to 9, which we concatenate to form larger integers with the understanding that we are using base 10, which means that each digit is worth ten times as much as the one to the right of it. For example:\n",
    "\n",
    "$$409 = 4\\times10^2 + 0\\times10^1 + 9\\times10^0 = 400 + 0 + 9 = 409$$\n",
    "\n",
    "\n",
    "The ancient [**Babylonian cuneiform number system**](https://en.wikipedia.org/wiki/Babylonian_cuneiform_numerals) (circa 2000 BCE) had a number system that was different in three ways: \n",
    "1. They had no symbol for zero. \n",
    "2. It was base 60, not base 10. \n",
    "3. Instead of having 60 different symbols for the digits, they had just two symbols, for 1 and 10, and they used a tally system where you just put together as many 10s and 1s as you need (in any order) to make a \"digit\" from 1 to 59. \n",
    "\n",
    "Here are some examples:\n",
    "\n",
    "![](https://xjwfriends.files.wordpress.com/2018/02/babyloniannumbers.jpg)\n",
    "\n",
    "We will use the Unicode cuneiform characters `𒐕` to represent 1, and  `𒌋`  to represent 10.  If you have trouble finding the right Unicode characters you can use `!` for 1 and `<` for 10.\n",
    "\n",
    "Thus,  59 can be represented as `𒌋𒌋𒌋𒌋𒌋𒐕𒐕𒐕𒐕𒐕𒐕𒐕𒐕𒐕` or `𒐕𒐕𒐕𒌋𒌋𒌋𒐕𒐕𒐕𒐕𒐕𒐕𒌋𒌋` or `<<<!!!!!<<!!!!`, for example.\n",
    "\n",
    "Numbers larger than 59 use base 60 notation, with a single space separating base-60  \"digits\". So we have: \n",
    "\n",
    "`𒐕 𒌋𒐕𒐕𒐕𒐕𒐕` = `[1, 15]` = $1\\times60^1 + 15\\times 60^0 = 60 + 15 = 75$\n",
    "\n",
    "and\n",
    "\n",
    "`𒌋 𒐕𒐕𒐕 𒌋𒐕`  = `[10, 3, 11]` = $10\\times60^2 + 3\\times60^1 + 11\\times60^0 = 36000 + 180 + 11 = 36191$\n",
    "\n",
    "Without a zero, how did the Babylonians represent a number that had zero in, say, the 60s place? Their answer was to have two consecutive spaces with nothing in between. This makes perfect sense, but it is extremely error-prone to visually distinguish one space from two. Fortunately it is easier on the computer than on a clay tablet.\n",
    "\n",
    "Here's a function to convert a Babylonian number string into an integer, with an auxiliary function to give the list of digits:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "\n",
    "value = {'𒐕': 1, '𒌋': 10, '!': 1, '<': 10}\n",
    "                     \n",
    "def babyl_to_int(num: str) -> int:\n",
    "    \"Convert a base 60 Babylonian number string into an integer.\"\n",
    "    return sum(d * 60 ** i for i, d in enumerate(reversed(digits(num))))\n",
    "\n",
    "def digits(num: str) -> List[int]:\n",
    "    \"The list of integer digits for a Babylonian number\"\n",
    "    return [sum(value[c] for c in group) for group in num.split(' ')]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "An example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "36191"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "babyl_to_int('𒌋 𒐕𒐕𒐕 𒌋𒐕')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[10, 3, 11]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "digits('𒌋 𒐕𒐕𒐕 𒌋𒐕')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now a function to go in the other direction:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def int_to_babyl(n: int) -> str:\n",
    "    \"\"\"Convert an integer into a base 60 Babylonian number string.\"\"\"\n",
    "    return ' '.join((d // 10) * '𒌋' + (d % 10) * '𒐕' \n",
    "                    for d in to_base(n, 60))\n",
    "    \n",
    "def to_base(n: int, base=60) -> List[int]:\n",
    "    \"\"\"The list of integer digits for `n` in base `base`.\"\"\"\n",
    "    return [n] if n < base else [*to_base(n // base, base), n % base]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'𒌋 𒐕𒐕𒐕 𒌋𒐕'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "int_to_babyl(36191)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[10, 3, 11]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "to_base(36191, 60)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is a more comprehensive test suite:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tests = [\n",
    "    (     0,          [0], \"\", \"  \"),\n",
    "    (     1,          [1], \"𒐕\", \"!\"),\n",
    "    (    11,         [11], \"𒌋𒐕\", \"<!\"),\n",
    "    (    59,         [59], \"𒌋𒌋𒌋𒌋𒌋𒐕𒐕𒐕𒐕𒐕𒐕𒐕𒐕𒐕\", \"𒐕𒐕𒐕𒌋𒌋𒌋𒐕𒐕𒐕𒐕𒐕𒐕𒌋𒌋\", \"<<<!!!!!<<!!!!\"),\n",
    "    (    60,       [1, 0], \"𒐕 \"),\n",
    "    (    61,       [1, 1], \"𒐕 𒐕\", \"! !\"),\n",
    "    (  3601,    [1, 0, 1], \"𒐕  𒐕\"),\n",
    "    (  4302,  [1, 11, 42], \"𒐕 𒌋𒐕 𒌋𒌋𒌋𒌋𒐕𒐕\", \"𒐕 𒐕𒌋 𒌋𒌋𒐕𒐕𒌋𒌋\", \"! !< <<!!<<\"),\n",
    "    ( 36191,  [10, 3, 11], \"𒌋 𒐕𒐕𒐕 𒌋𒐕\"),\n",
    "    (201600,   [56, 0, 0], \"𒌋𒌋𒌋𒌋𒌋𒐕𒐕𒐕𒐕𒐕𒐕  \"),\n",
    "    (223384, [1, 2, 3, 4], \"𒐕 𒐕𒐕 𒐕𒐕𒐕 𒐕𒐕𒐕𒐕\")]\n",
    "\n",
    "def test(tests=tests) -> bool:\n",
    "    \"\"\"Each test is an integer `n`, the digits in base 60, and \n",
    "    one or more ways of representing `n` as a Babylonian number strings.\"\"\"\n",
    "    for (n, base60_digits, *nums) in tests:\n",
    "        assert int_to_babyl(n) in nums, f'int_to_babyl({n})'\n",
    "        assert digits(nums[0]) == to_base(n, 60) == base60_digits, f'digits({nums[0]!r})'\n",
    "        for num in nums:\n",
    "            assert babyl_to_int(num) == n, f'babyl_to_int({num!r})'\n",
    "    return True\n",
    "    \n",
    "test()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
